package pl.un4giv3n.mightvsmagic.utils;

import gnu.trove.TLongHashSet;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import javax.persistence.EntityManager;
import javax.persistence.Query;

import pl.un4giv3n.mightvsmagic.entity.Item;
import pl.un4giv3n.mightvsmagic.entity.SpecialSkill;
import pl.un4giv3n.mightvsmagic.entity.SpecialSkillTemplate;
import pl.un4giv3n.mightvsmagic.entity.User;
import pl.un4giv3n.mightvsmagic.entity.UserAttribute;
import pl.un4giv3n.mightvsmagic.entity.UserType;

/**
 * @author andrzejewskia
 * Support class for manipulating user data
 */
public final class UserUtil {
	static final String DEFAULT_KNIGHT_AVATAR = "./img/avatars/knight.jpg";
	static final String DEFAULT_MAGE_AVATAR= "./img/avatars/mage.jpg";
	public static final int NEAR_ATTR = 25;
	public static final int NEAR_EXP = 400;
	private UserUtil() {
	}

	/**
	 * @param passwd
	 * @return true if password matches requirements
	 */
	public static boolean validatePassword(final String passwd) {
		if (passwd.length() < 6) {
			return false;
		}
		if (!passwd.matches("^(?=.*[a-zA-Z])(?=.*[0-9])[a-zA-Z0-9_-]{6,24}$")) {
			return false;
		}
		return true;
	}
	
	public static String encyptPassword(final String passwd){
		MessageDigest md;
		try {
			md = MessageDigest.getInstance("SHA1");
			md.update(passwd.getBytes()); 
			byte[] output = md.digest();
			return FormatterUtil.byteArray2Hex(output);
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		}
		return "";
	}
	
	/**
	 * @param email
	 * @return true if is valid email
	 */
	public static boolean validateEmail(final String email) {
		return email.matches(".+@.+\\.[a-z]+");
	}

	/**
	 * @param login
	 * @return true if login matches requirements
	 */
	public static boolean validateLogin(final String login) {
		if (login.length() < 1) {
			return false;
		}
		if (!login.matches("^(?=.*[a-zA-Z0-9_-])[a-zA-Z0-9_-]{1,24}$")) {
			return false;
		}
		return true;
	}
	
	/**
	 * @param login - login to check
	 * @param entityManager - manager to query db
	 * @return true if login is free
	 */
	public static boolean isLoginFree(final String login,
			final EntityManager entityManager) {
		Query query = entityManager.createNamedQuery("find.user.by.login");
		query.setParameter("login", login);
		return query.getResultList() == null || query.getResultList().isEmpty();
	}
	
	public static boolean isEmailFree(final String email, final EntityManager em){
		Query q = em.createNamedQuery("find.user.by.email");
		q.setParameter("email", email);
		return q.getResultList().isEmpty();
	}

	/**
	 * @param user
	 * @return user base min dmg
	 */
	public static double getBaseMinDamage(final User user) {
		double minDmg = 0.0d;
		Set<SpecialSkill> skills = user.getSkills();
		//first calculate typical dmg
		for (SpecialSkill specialSkill : skills) {
			if(specialSkill.getMinDmgIncrease() != 0){
				minDmg += specialSkill.getMinDmgIncrease();
			}
		}
		return minDmg;
	}

	/**
	 * @param user
	 * @return user max base dmg
	 */
	public static double getBaseMaxDamage(final User user) {
		double maxDmg = 1.0d;
		Set<SpecialSkill> skills = user.getSkills();
		for (SpecialSkill specialSkill : skills) {
			if(specialSkill.getMaxDmgIncrease() != 0){
				maxDmg += specialSkill.getMaxDmgIncrease();
			}
		}
		return maxDmg;
	}

	/**
	 * @param user
	 * @return user min base defence
	 */
	public static double getBaseMinDefence(final User user) {
		double minDef = 0.0d;
		Set<SpecialSkill> skills = user.getSkills();
		for (SpecialSkill specialSkill : skills) {
			if(specialSkill.getMinDefIncrease() != 0){
				minDef += specialSkill.getMinDefIncrease();
			}
		}
		return minDef;
	}

	/**
	 * @param user
	 * @return user base max defence
	 */
	public static double getBaseMaxDefence(final User user) {
		double maxDef = 0.0d;
		Set<SpecialSkill> skills = user.getSkills();
		for (SpecialSkill specialSkill : skills) {
			if(specialSkill.getMaxDefIncrease() != 0){
				maxDef += specialSkill.getMaxDefIncrease();
			}
		}
		return maxDef;
	}

	/**
	 * @param user
	 * @return user total min damage
	 */
	public static double getTotalMinDamage(final User user) {
		double dmg = getBaseMinDamage(user) + ItemUtil.getMinItemDamage(user);
		dmg +=getIceDamage(user)+getFireDamage(user)+getEarthDamage(user)+getWindDamage(user);
		return factorizeDamage(dmg, user);
	}
	
	/**
	 * @param user
	 * @return user total max damage
	 */
	public static double getTotalMaxDamage(final User user) {
		double dmg = getBaseMaxDamage(user) + ItemUtil.getMaxItemDamage(user);
		dmg +=getIceDamage(user)+getFireDamage(user)+getEarthDamage(user)+getWindDamage(user);
		return factorizeDamage(dmg, user);
	}
	
	public static double factorizeDamage(double baseDmg, final User user){
		double returnVal = baseDmg; 
		for (SpecialSkill skill : user.getSkills()) {
			returnVal+=(returnVal*(skill.getDmgFactor()/100));
		}
		return returnVal;
	}
	
	public static double factorizeDefence(double baseDef, final User user){
		double returnVal = baseDef; 
		for (SpecialSkill skill : user.getSkills()) {
			returnVal+=(returnVal*(skill.getDefFactor()/100));
		}
		return returnVal;
	}
	
	public static double getIceDamage(final User user){
		double dmg = 0.0d;
		for (SpecialSkill skill : user.getSkills()) {
			dmg+= skill.getIceDamage();
		}
		if(user.getClan() != null){
			dmg += ClanUtil.getWaterBonus(user.getClan());
		}
		return dmg;
	}
	public static double getFireDamage(final User user){
		double dmg = 0.0d;
		for (SpecialSkill skill : user.getSkills()) {
			dmg+= skill.getFireDamage();
		}
		if(user.getClan() != null){
			dmg += ClanUtil.getFireBonus(user.getClan());
		}
		return dmg;
	}
	public static double getEarthDamage(final User user){
		double dmg = 0.0d;
		for (SpecialSkill skill : user.getSkills()) {
			dmg+= skill.getEarthDamage();
		}
		if(user.getClan() != null){
			dmg += ClanUtil.getEarthBonus(user.getClan());
		}
		return dmg;
	}
	public static double getWindDamage(final User user){
		double dmg = 0.0d;
		for (SpecialSkill skill : user.getSkills()) {
			dmg+= skill.getWindDamage();
		}
		if(user.getClan() != null){
			dmg += ClanUtil.getWindBonus(user.getClan());
		}
		return dmg;
	}
	public static double getIceDefence(final User user){
		double def = 0.0d;
		for (SpecialSkill skill : user.getSkills()) {
			def+= skill.getIceDefence();
		}
		if(user.getClan() != null){
			def += ClanUtil.getWaterBonus(user.getClan());
		}
		return def;
	}
	public static double getFireDefence(final User user){
		double def = 0.0d;
		for (SpecialSkill skill : user.getSkills()) {
			def+= skill.getFireDefence();
		}
		if(user.getClan() != null){
			def += ClanUtil.getFireBonus(user.getClan());
		}
		return def;
	}
	public static double getEarthDefence(final User user){
		double def = 0.0d;
		for (SpecialSkill skill : user.getSkills()) {
			def+= skill.getEarthDefence();
		}
		if(user.getClan() != null){
			def += ClanUtil.getEarthBonus(user.getClan());
		}
		return def;
	}
	public static double getWindDefence(final User user){
		double def = 0.0d;
		for (SpecialSkill skill : user.getSkills()) {
			def+= skill.getWindDefence();
		}
		if(user.getClan() != null){
			def += ClanUtil.getWindBonus(user.getClan());
		}
		return def;
	}

	/**
	 * @param user
	 * @return user total min defense
	 */
	public static double getTotalMinDefense(final User user) {
		double def = getBaseMinDefence(user) + ItemUtil.getMinItemDefence(user);
		def +=getIceDefence(user)+getFireDefence(user)+getEarthDefence(user)+getWindDefence(user);
		return factorizeDefence(def, user);
	}

	/**
	 * @param user
	 * @return user total max defense
	 */
	public static double getTotalMaxDefense(final User user) {
		double def = getBaseMaxDefence(user) + ItemUtil.getMaxItemDefence(user);
		def +=getIceDefence(user)+getFireDefence(user)+getEarthDefence(user)+getWindDefence(user);
		return factorizeDefence(def, user);
	}


	public static long getUserSkillLevel(User user, UserAttribute attr){
		List<Item> items = ItemUtil.getUserEquipedItems(user);
		Set<SpecialSkill> skills = user.getSkills();
		long lvl = 0;
		switch(attr){
		case STRENGHT:
			lvl = user.getStrenght();
			for (Item item : items) {
				if(item.isInUse()){
					lvl+=item.getStrenghtModifier();
				}
			}
			for (SpecialSkill specialSkill : skills) {
				lvl+=specialSkill.getStrenghtModifier();
			}
			if(user.getClan() != null){
				lvl += ClanUtil.getStrengthBonus(user.getClan());
			}
			return lvl > 0 ? lvl : 0;
		case STAMINA:
			lvl = user.getStamina();
			for (Item item : items) {
				if(item.isInUse()){
					lvl+=item.getStaminaModifier();
				}
			}
			for (SpecialSkill specialSkill : skills) {
				lvl+=specialSkill.getStaminaModifier();
			}
			if(user.getClan() != null){
				lvl += ClanUtil.getStaminaBonus(user.getClan());
			}
			return lvl > 0 ? lvl : 0;
		case MANA:
			lvl = user.getMana();
			for (Item item : items) {
				if(item.isInUse()){
					lvl+=item.getManaModifier();
				}

			}
			for (SpecialSkill specialSkill : skills) {
				lvl+=specialSkill.getManaModifier();
			}
			if(user.getClan() != null){
				lvl += ClanUtil.getManaBonus(user.getClan());
			}
			return lvl > 0 ? lvl : 0;
		case ATTACK:
			lvl = user.getAttack();
			for (Item item : items) {
				if(item.isInUse()){
					lvl+=item.getAttackModifier();
				}

			}
			for (SpecialSkill specialSkill : skills) {
				lvl+=specialSkill.getAttackModifier();
			}
			if(user.getClan() != null){
				lvl += ClanUtil.getAttackBonus(user.getClan());
			}
			return lvl > 0 ? lvl : 0;
		case PARRY:
			lvl = user.getParry();
			for (Item item : items) {
				if(item.isInUse()){
					lvl+=item.getParryModifier();
				}
			}
			for (SpecialSkill specialSkill : skills) {
				lvl+=specialSkill.getParryModifier();
			}
			if(user.getClan() != null){
				lvl += ClanUtil.getParryBonus(user.getClan());
			}
			return lvl > 0 ? lvl : 0;
		case LIFE:
			lvl = user.getLife();
			if(user.getClan() != null){
				lvl += ClanUtil.getLifeBonus(user.getClan());
			}
			return lvl > 0 ? lvl : 0;
		case SPEED:
			lvl = user.getCalculatedSpeed();
			for (Item item : items) {
				if(item.isInUse()){
					lvl+=item.getSpeedModifier();
				}
			}
			for (SpecialSkill specialSkill : skills) {
				lvl+=specialSkill.getSpeedModifier();
			}
			for (SpecialSkill specialSkill : skills) {
				lvl= Math.round(lvl + lvl*specialSkill.getSpeedFactor());
			}
			if(user.getClan() != null){
				lvl += ClanUtil.getSpeedBonus(user.getClan());
			}
			return lvl > 999 ? 999 : lvl;
		default:
			throw new IllegalArgumentException("Trying to count illegal skill type");
		}
	}
	
	public static long getNextSkillCost(User user, UserAttribute attr){
		long lvl;
		long base = 0;
		if (attr == UserAttribute.STAMINA
				|| attr == UserAttribute.MANA) {
			base = 1250;
		}else{
			base = 750;
		}
		switch(attr){
		case STRENGHT:
			lvl = user.getStrenght();
			return lvl*lvl+base;
		case STAMINA:
			lvl = user.getStamina();
			if(user.getUserType() == UserType.MAGE){
				return lvl*lvl*base;
			}else{
				return lvl*lvl+base;
			}
		case MANA:
			lvl = user.getMana();
			if(user.getUserType() == UserType.MAGE){
				return lvl*lvl+base;
			}else{
				return lvl*lvl*base;
			}
		case ATTACK:
			lvl = user.getAttack();
			return lvl*lvl+base;
		case PARRY:
			lvl = user.getParry();
			return lvl*lvl+base;
		case LIFE:
			lvl = user.getLife();
			return lvl*lvl+base;
		default:
			throw new IllegalArgumentException("Trying to find illegal skill type");
		}
	}
	
	/**
	 * finds skill templates that user can learn (by his attributes)
	 * and filters out already learnt skills
	 * 
	 * @param user user to find templates
	 * @param em entity manager to find templates
	 * @return
	 */
	public static List<SpecialSkillTemplate> getAvaibleToLearnSpecialSkills(
			User user, EntityManager em) {
		Query query = em.createNamedQuery("find.template.for.user");
		query.setParameter("strenghtParam", user.getStrenght()+getSkillStrenghtBonus(user));
		query.setParameter("staminaParam", user.getStamina()+getSkillStaminaBonus(user));
		query.setParameter("manaParam", user.getMana()+getSkillManaBonus(user));
		query.setParameter("attackParam", user.getAttack()+getSkillAttackBonus(user));
		query.setParameter("parryParam", user.getParry()+getSkillParryBonus(user));
		query.setParameter("requiredFightExpParam", user.getFightExp());
		query.setParameter("requiredMagicExpParam", user.getMagicExp());
		List<SpecialSkillTemplate> resultList = clearIrrelevantSkills(user,
				query);
		return resultList;
	}

	@SuppressWarnings("unchecked")
	private static List<SpecialSkillTemplate> clearIrrelevantSkills(User user,
			Query query) {
		List<SpecialSkillTemplate> resultList = new ArrayList<SpecialSkillTemplate>(query.getResultList());
		for (Iterator<SpecialSkillTemplate> iterator = resultList.iterator(); iterator.hasNext();) {
			SpecialSkillTemplate template = iterator.next();
			if(UserUtil.isSkillKnownToUser(user,template) || !UserUtil.userHasRequiredSkills(user,template) || UserUtil.learningBlockedByOtherSkills(user,template)){
				iterator.remove();
			}
		}
		return resultList;
	}
	/** finds skill templates that user will be able to learn in near future
	* 
	* @param user user to find templates
	* @param em entity manager to find templates
	* @return
	*/
	public static List<SpecialSkillTemplate> getNextAvaibleToLearnSpecialSkills(
			User user, EntityManager em) {
		Query query = em.createNamedQuery("find.template.for.user");
		query.setParameter("strenghtParam", user.getStrenght()+ getSkillStrenghtBonus(user)+NEAR_ATTR);
		query.setParameter("staminaParam", user.getStamina()+getSkillStaminaBonus(user)+NEAR_ATTR);
		query.setParameter("manaParam", user.getMana()+getSkillManaBonus(user)+NEAR_ATTR);
		query.setParameter("attackParam", user.getAttack()+getSkillAttackBonus(user)+NEAR_ATTR);
		query.setParameter("parryParam", user.getParry()+getSkillParryBonus(user)+NEAR_ATTR);
		query.setParameter("requiredFightExpParam", user.getFightExp()+NEAR_EXP);
		query.setParameter("requiredMagicExpParam", user.getMagicExp()+NEAR_EXP);
		List<SpecialSkillTemplate> resultList = clearIrrelevantSkills(user,
				query);
		return resultList;
	}

	private static boolean learningBlockedByOtherSkills(User user,
			SpecialSkillTemplate template) {
		for (SpecialSkill skill : user.getSkills()) {
			long[] blocksLearning = skill.getBlocksLearning();
			if(blocksLearning == null){
				continue;
			}else{
				for (long idBlocked : blocksLearning) {
					if(idBlocked == template.getId().longValue()){
						return true;
					}
				}
			}
		}
		return false;
	}

	public static boolean userHasRequiredSkills(User user,
			SpecialSkillTemplate template) {
		if( template.getRequiresToLearn() == null){
			return true;
		}
		TLongHashSet knownSkills = getUserKnownSkillsIds(user);
		//for each required id
		for (long id : template.getRequiresToLearn()) {
			//if knownSkills doest have the id return false
			if(!knownSkills.contains(id)){
				return false;
			}
		}
		return true;
	}

	public static TLongHashSet getUserKnownSkillsIds(User user) {
		TLongHashSet knownSkills = new TLongHashSet();
		for (SpecialSkill skill : user.getSkills()) {
			knownSkills.add(skill.getTemplateId());
		}
		return knownSkills;
	}

	public static boolean isSkillKnownToUser(User user,
			SpecialSkillTemplate template) {
		for (SpecialSkill skill : user.getSkills()) {
			if(skill.getTemplateId().longValue() == template.getId().longValue()){
				return true;
			}
		}
		return false;
	}
	
	public static String getPremiumEndTimeInfo(User user){
		String returnVal;
		long premiumEndTime = user.getPremiumEndTime();
		if(premiumEndTime == 0 || premiumEndTime < System.currentTimeMillis()){
			returnVal = "Twoje konto premium nie jest aktywne.";
		}else{
			Date date = new Date(premiumEndTime);
			returnVal = "Twoje konto premium jest aktywne do " + FormatterUtil.formatDate(date);
		}
		return returnVal;
	}
	
	public static long getSkillParryBonus(User user){
		long val = 0;
		for (SpecialSkill skill : user.getSkills()) {
			val+=skill.getParryModifier();
		}
		return val;
	}
	public static long getSkillAttackBonus(User user){
		long val = 0;
		for (SpecialSkill skill : user.getSkills()) {
			val+=skill.getAttackModifier();
		}
		return val;
	}
	public static long getSkillManaBonus(User user){
		long val = 0;
		for (SpecialSkill skill : user.getSkills()) {
			val+=skill.getManaModifier();
		}
		return val;
	}
	public static long getSkillStaminaBonus(User user){
		long val = 0;
		for (SpecialSkill skill : user.getSkills()) {
			val+=skill.getStaminaModifier();
		}
		return val;
	}
	public static long getSkillStrenghtBonus(User user){
		long val = 0;
		for (SpecialSkill skill : user.getSkills()) {
			val+=skill.getStrenghtModifier();
		}
		return val;
	}
	public static long getSkillSpeedBonus(User user){
		long val = 0;
		for (SpecialSkill skill : user.getSkills()) {
			val+=skill.getSpeedModifier();
		}
		return val;
	}
}
